﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using System;
using System.Collections.Generic;
using System.Console;
using System.Linq;
using StringTemplate;
using NRails.Database.Schema;
using System.Text.RegularExpressions;
using NRails.Macros;

[StringTemplateGroup]
public class ClassGenerator
{
    public this()
    {
    }

    public Generate(namespaceName : string, nameMigration : string, usings : list[string], dbSchema : DBSchema) : string
    {
        def comparer = TableSchemaComparer();
        def tables = dbSchema.Tables.Filter(t => t.Name != "SchemaMigrations")
            .Sort(comparer.Compare);

        def makeTableDef(table : TableSchema) 
        {
            def foreignKeys = table.Keys
              .Filter(k => k.KeyType == ConstraintType.KeyForeign);
              
            def column(col : TableColumnSchema) 
            {
                mutable tp = col.Type.DbType.ToString();
                mutable attrs = [];
                
                when (col.Nullable)
                    tp += "?";
                
                match (col)
                {
                  | col when col.Size > 0 => 
                    attrs ::= $"len = $(col.Size)";
                  | col when col.Type.Precision > 0 || col.Type.Precision > 0 =>
                    attrs ::= $"precision = $(col.Type.Precision), seed = $(col.Type.Scale)";
                  | _ => ()
                }

                match (table.KeyPrimary())
                {
                  | null => ();
                  | x when x.Columns.Split(',').Map(_.Trim()).Contains(col.Name) =>
                    attrs ::= "pk";
                }
                
                when (col.AutoIncrement)
                    attrs ::= $"identity($(col.Seed), $(col.Increment))";

                def attrString = if (attrs.Any())
                    "(" + String.Join(", ", attrs.Rev().ToArray()) + ")"
                else
                    String.Empty;
                    
                tp += attrString;
                
                def isDefaultConstrint(k, colName)
                {
                  k.Columns == colName && k.KeyType == ConstraintType.Default
                }

                when (table.Keys.Filter(isDefaultConstrint(_, col.Name)).Any())
                {
                  def df = match (col.DefaultValue)
                  {
                      | x when x.StartsWith("'") => "\"" + x.Trim('\'') + "\""
                      | x => x
                  }
                  tp = $"$tp = $df";
                }
                  
                (col.Name, tp)
            }
            
            def foreignKey(key : KeySchema) 
            {
                (key.Columns, key.RelTable)
            }
            
            def isForeignKey(col)
            {
              match (foreignKeys.Filter(fk => fk.Columns == col.Name))
              {
                | [] => false
                | _ => true
              }
            }

            def columns = table.Columns
              .Filter(c => !isForeignKey(c))
              .Map(column)
              .ToList();
              
            def keys = foreignKeys.Map(foreignKey);

            (table.Name, columns, keys)
        }; 
        def tablesAndColumns = tables.Map(makeTableDef);
        def forDrop = tables.ToListRev().Map(_.Name);
        Generate(namespaceName, nameMigration, usings, tablesAndColumns, forDrop);
    }

    class TableSchemaComparer : IComparer[TableSchema]
    {
        public Compare(x : TableSchema, y : TableSchema) : int
        {
            def links(t)
            {
                t.Keys.Filter(t => t.KeyType == ConstraintType.KeyForeign).Map(_.RelTable)
            }
            if (links(x).Contains(y.Name))
                1
            else
                if (links(y).Contains(x.Name))
                    -1
                else
                    0
        }
    }

    public Generate(_namespaceName : string, _nameMigration : string, _usings : list[string],
        _tablesColumnsKeys : list[string*list[string*string]*list[string*string]], // TODO: ugly design
        _forDrop : list[string]) : string
    {
    <#..$(_usings; "\n"; GenerateUsing)
            
namespace $_namespaceName
{
    public class $(GetClassname(_nameMigration)) : Migration
    {
        public this () {base("$(GetClassname(_nameMigration))")}

        protected override Up() : void
        {
        ..$(_tablesColumnsKeys; "\n"; CreateTable)
        }

        protected override Down() : void
        {
        ..$(_forDrop; "\n"; DropTable)
        }
    }        
}
#>}

    public GenerateMigration(_namespaceName : string, _nameMigration : string, _usings : list[string]) : string
    {<#..$(_usings; "\n"; GenerateUsing)
            
namespace $_namespaceName
{
    public class $(GetClassname(_nameMigration)) : Migration
    {
        public this () {base("$(GetClassname(_nameMigration))")}

        protected override Up() : void
        {
        }

        protected override Down() : void
        {
        }
    }        
}
#>}

    public GenerateController(_namespaceName : string, _name : string, _usings : list[string]) : string
    {<#..$(_usings; "\n"; GenerateUsing)
            
namespace $_namespaceName.Controllers
{
    [HandleError]
    public class $_name : Controller
    {
        public Index() : ActionResult
        {
        }
    }        
}
#>}

    public GenerateView(_name : string, _usings : list[string]) : string
    {<#<%@ Page Language="Nemerle" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="$(_name)Title" ContentPlaceHolderID="TitleContent" runat="server">
</asp:Content>

<asp:Content ID="$(_name)Content" ContentPlaceHolderID="MainContent" runat="server">
</asp:Content>
#>}

    GenerateUsing(name : string) : string
    {
       $"using $name;"
    }

    CreateColumn(name : string, type_: string) : string
    {
       $"$name : $type_;"
    }

    CreateKey(name : string, type_: string) : string
    {
       $"$name :> $type_;"
    }

    CreateTable(_name : string, _columns : list[string * string], _keys : list[string*string]) : string
    {<#
    create $_name
    {
      ..$(_columns; "\n"; CreateColumn)
      ..$(_keys; "\n"; CreateKey)
    }
#>}

    DropTable(_name : string) : string
    {<#
    drop $_name;
#>}

    GetClassname(name : string) :string
    {
       name.Substring(0,name.Length-2)
    }    
}
